package com.erlang4j.internal.messages;

import static com.erlang4j.internal.messages.MessageTokenType.atomName;
import static com.erlang4j.internal.messages.MessageTokenType.closeList;
import static com.erlang4j.internal.messages.MessageTokenType.closeTuple;
import static com.erlang4j.internal.messages.MessageTokenType.openList;
import static com.erlang4j.internal.messages.MessageTokenType.openTuple;
import static com.erlang4j.internal.messages.MessageTokenType.variable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import com.erlang4j.api.exceptions.Erlang4jMalformedPattern;
import com.erlang4j.api.exceptions.Erlang4jUnexpectedEndOfTokens;

/**
 * <p>
 * This is pretty straight forwards lexical analyser. It also validates the tokens using a stack to check that all the
 * opened tuples/lists have been closed and vice verse
 * </p>
 * <p>
 * Design notes
 * <ul>
 * <li>The validation of open/close is done here because there are multiple users of this, and I wanted to centralise
 * the code... It feels a bit wrong to have a lexical analyser do grammer checking, so at some later date this may move
 * from here
 * <li>The Character.isUppercase / isJavaIdentifierPart is used when parsing Variables and atoms. These don't correspond
 * exactly to the Erlang definitions...but it works well for the subset I use
 * </ul>
 * </p>
 * 
 * @author Phil Rice
 */
public class MessageLexicalAnalyser implements Iterable<MessageToken> {

	private final List<MessageToken> result;
	private final String input;
	private int i = 0;

	public MessageLexicalAnalyser(final String input) {
		this.input = input;
		result = new ArrayList<MessageToken>();
		while (i < input.length()) {
			char ch = input.charAt(i++);
			if (ch == '{')
				result.add(new MessageToken(input, i, openTuple, "{"));
			else if (ch == '}')
				result.add(new MessageToken(input, i, closeTuple, "}"));
			else if (ch == '[')
				result.add(new MessageToken(input, i, openList, "["));
			else if (ch == ']')
				result.add(new MessageToken(input, i, closeList, "]"));
			else if (Character.isUpperCase(ch))
				result.add(findNext(variable));
			else if (Character.isLowerCase(ch))
				result.add(findNext(atomName));
			else if (Character.isWhitespace(ch) || ch == ',')
				;// ignore white space and commas
			else
				throw new IllegalArgumentException(String.format("Cannot process character [%s] pos [%s] in [%s]", ch, i - 1, input));
		}
		validateOpenClose();
	}

	private void validateOpenClose() {
		Stack<MessageTokenType> stack = new Stack<MessageTokenType>();
		for (MessageToken token : this) {
			switch (token.type) {
			case openList:
			case openTuple:
				stack.push(token.type);
				break;
			case closeList:
				checkTopToken(stack, token, openList, closeList);
				break;
			case closeTuple:
				checkTopToken(stack, token, openTuple, closeTuple);
				break;
			default:
				;
			}
		}
		if (!stack.isEmpty())
			throw new Erlang4jUnexpectedEndOfTokens(input, stack);

	}

	private void checkTopToken(Stack<MessageTokenType> stack, MessageToken token, MessageTokenType open, MessageTokenType expected) {
		if (stack.isEmpty())
			throw new Erlang4jMalformedPattern(token);
		MessageTokenType top = stack.pop();
		if (top != open)
			throw new Erlang4jMalformedPattern(token);

	}

	private MessageToken findNext(MessageTokenType type) {
		int start = i;
		while (i < input.length() && Character.isJavaIdentifierPart(input.charAt(i)))
			i++;
		String result = input.substring(start - 1, i);
		return new MessageToken(input, i, type, result);
	}

	@Override
	public Iterator<MessageToken> iterator() {
		return result.iterator();
	}
}
